package app

import (
	"fmt"
	"strings"
	"time"

	"github.com/astaxie/beego"
	"github.com/sirupsen/logrus"

	"cvevulner/cve-ddd/domain"
	"cvevulner/cve-ddd/domain/bulletin"
	"cvevulner/cve-ddd/domain/hotpatch"
	"cvevulner/cve-ddd/domain/obs"
	"cvevulner/cve-ddd/domain/repository"
	"cvevulner/cve-ddd/domain/service"
	"cvevulner/cve-ddd/domain/testresult"
	"cvevulner/cve-ddd/domain/updateinfo"
)

const (
	hotPatchUpdateInfoDir      = "updateinfo-hotpatch"
	hotPatchEarlyUpdateInfoDir = "hotpatch-early-updateinfo"
)

type RefactorHotPatchService interface {
	GenerateBulletins(uploadDir, date string) error
}

func NewRefactorHotPatchService(
	r repository.CveRepository,
	b bulletin.Bulletin,
	o obs.OBS,
	u updateinfo.UpdateInfo,
	h hotpatch.HotPatch,
	l *logrus.Entry,
	t testresult.Result,
) *refactorHotPatchService {
	return &refactorHotPatchService{
		repository: r,
		bulletin:   b,
		obs:        o,
		updateInfo: u,
		hotPatch:   h,
		log:        l,
		testResult: t,
	}
}

type refactorHotPatchService struct {
	obs        obs.OBS
	log        *logrus.Entry
	hotPatch   hotpatch.HotPatch
	bulletin   bulletin.Bulletin
	testResult testresult.Result
	updateInfo updateinfo.UpdateInfo
	repository repository.CveRepository
}

func (h *refactorHotPatchService) GenerateBulletins(uploadDir, date string) error {
	issues, err := h.hotPatch.GetIssueInfo()
	if err != nil {
		return err
	}

	var cvesForUpdateInfo domain.Cves
	var uploadFileName []string

	maxHotPatchId, err := h.hotPatch.MaxHotPatchID()
	if err != nil {
		return fmt.Errorf("parse max id failed: %w", err)
	}

	// 热补丁有专属目录
	hotDate := "hotpatch_" + date

	for _, issue := range issues {
		isHotPatchExists := h.testResult.IsHotPatchExist(&issue, hotDate)
		if !isHotPatchExists {
			h.log.Errorf("hot patch source rpm of %s not exist", issue.HotIssueNum)
			continue
		}

		isPublished, err1 := h.hotPatch.IsPublished(issue.CveNum[0], issue.Component, issue.Branch)
		if err1 != nil {
			h.log.Errorf("check whether hotpatch %s is published, occurred error: %s", issue.HotIssueNum, err1.Error())
			return err
		}

		if isPublished {
			h.log.Errorf("hotpatch %s is published", issue.HotIssueNum)
			continue
		}

		cves, err1 := h.repository.FindCves(
			repository.Option{
				CveNum:    issue.CveNum,
				Component: issue.Component,
			})
		if err1 != nil {
			h.log.Errorf("find cve %s, error %s", issue.CveNum, err1.Error())
			return err
		}
		if len(cves) == 0 {
			h.log.Errorf("find cve %s nil", issue.CveNum)
			continue
		}
		// all cves have the same hot issue number
		for k := range cves {
			cves[k].HotIssueNum = issue.HotIssueNum
			cves[k].AffectedVersion = []string{issue.Branch}
			cves[k].Component = issue.Component
		}

		// 将正式目录的updateinfo文件同步到early目录，这样每次提前生成的文件都是以正式目录为基准的
		service.SyncUpdateInfoToEarlyDir(h.obs, h.log, domain.GetMaintainVersion(),
			hotPatchUpdateInfoDir, hotPatchEarlyUpdateInfoDir)

		bulletins := cves.GenerateBulletins()
		for _, b := range bulletins {
			b.PatchUrl = issue.PatchUrl

			maxHotPatchId++
			b.SetIdentificationOfHotPatch(maxHotPatchId)

			xmlData, err2 := h.bulletin.GenerateHotPatch(&b)
			if err2 != nil {
				h.log.Errorf("component: %s, to xml error: %s", b.Component, err2.Error())
				continue
			}

			path := uploadDir + b.CvrfFileName()
			if err2 = h.obs.Upload(path, xmlData); err2 != nil {
				h.log.Errorf("component: %s, upload to obs error: %s", b.Component, err2.Error())

				continue
			}

			uploadFileName = append(uploadFileName, b.PathAppendToIndexFile())

			cvesForUpdateInfo = append(cvesForUpdateInfo, b.Cves...)

			service.UploadUpdateInfoFile(h.obs, h.updateInfo, h.log, &b, hotDate, hotPatchEarlyUpdateInfoDir)
		}
	}

	if len(uploadFileName) == 0 {
		return fmt.Errorf("no new hot patch issues")
	}

	h.uploadIndexAndFixed(uploadDir, uploadFileName)

	return h.uploadUpdateInfo(cvesForUpdateInfo)
}

// getIndexContent 热补丁生成的公告文件要和冷补丁的公告文件名需上传同一个目录，追加到同一个index.txt文件下，
// 因此热补丁直接下载之前冷补丁逻辑修改后的index.txt文件
func (h *refactorHotPatchService) getIndexContent(uploadDir string) (string, error) {
	path := uploadDir + fileIndex
	content, err := h.obs.Download(path)

	return string(content), err
}

func (h *refactorHotPatchService) uploadIndexAndFixed(uploadDir string, hotPatchFiles []string) {
	indexPath := uploadDir + fileIndex
	indexContent, err := h.obs.Download(indexPath)
	if err != nil {
		logrus.Errorf("download %s failed: %s", fileIndex, err.Error())
		return
	}

	updateHotPatchContent := strings.TrimSpace(strings.Join(hotPatchFiles, EOF))
	newIndexContent := strings.TrimSpace(string(indexContent)) + EOF + updateHotPatchContent
	if err = h.obs.Upload(indexPath, []byte(newIndexContent)); err != nil {
		h.log.Errorf("upload %s failed: %v", fileIndex, err)
	}

	updateFixedPath := uploadDir + fileUpdateFixed
	fixedContent, err := h.obs.Download(updateFixedPath)
	if err != nil {
		logrus.Errorf("download %s failed: %s", fileUpdateFixed, err.Error())
		return
	}

	newFixedContent := strings.TrimSpace(string(fixedContent)) + EOF + updateHotPatchContent
	if err = h.obs.Upload(updateFixedPath, []byte(newFixedContent)); err != nil {
		h.log.Errorf("upload %s failed: %v", fileUpdateFixed, err)
	}
}

func (h *refactorHotPatchService) uploadUpdateInfo(cves domain.Cves) error {
	if len(cves) == 0 {
		return nil
	}

	dir := beego.AppConfig.String("obs::upload_updateinfo_dir")
	nowStr := time.Now().Format("2006-01-02")

	for version, v := range cves.GroupByVersion() {
		bytes, err := h.updateInfo.Generate(v)
		if err != nil {
			h.log.Errorf("generate updateinfo of %s error %s", version, err.Error())
			continue
		}

		path := fmt.Sprintf("%s%s-%s/%s", dir, nowStr, "hotpatch", fmt.Sprintf("%s_updateinfo.xlsx", version))
		if err := h.obs.Upload(path, bytes); err != nil {
			h.log.Errorf("version: %s, upload to obs error: %s", version, err.Error())
			continue
		}
	}

	return nil
}
